using System;

namespace ActiveSupport.Calculations
{
    public static class DateCalculationsExtensions
    {
        public const Int32 DaysInWeek = 7;
        public const Int32 DaysInWeekCalcOffSet = 1;

        /// <summary>
        /// Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning 
        /// of the day (0:00) and then subtracts the specified number of seconds
        /// </summary>
        public static DateTime Ago(this DateTime date, Int32 seconds)
        {
            return date.AddSeconds(seconds);
        }

        public static DateTime BeginningOfDay(this DateTime date)
        {
            return new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, 0);
        }

        /// <summary>
        /// Returns a new DateTime objects will have time set to 0:00
        /// DateTime representing the start of the month (1st of the month
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime BeginningOfMonth(this DateTime date)
        {
            return new DateTime(date.Year, date.Month, 1, 0, 0, 0, 0);
        }

        public static DateTime BeginningOfQuarter(this DateTime date)
        {
            var quarterMonth = (date.Month - 1)/3*3 + 1;
            return new DateTime(date.Year, quarterMonth, 1, 0, 0, 0, 0);
        }

        /// <summary>
        /// Returns a new Date/DateTime representing the "start" of this week 
        /// (i.e, Monday; DateTime objects will have time set to 0:00)
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime BeginningOfWeek(this DateTime date)
        {
            var dayOfWeek = (Int32) date.DayOfWeek;
            var daysTillStartWeek = (dayOfWeek - DaysInWeek) + DaysInWeekCalcOffSet;
            var newDate = date.AddDays(daysTillStartWeek);

            return new DateTime(newDate.Year, newDate.Month, newDate.Day, 0, 0, 0, 0);
        }

        /// <summary>
        /// Returns a new Date/DateTime representing the start of the year 
        /// (1st of january; DateTime objects will have time set to 0:00)
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime BeginningOfYear(this DateTime date)
        {
            return new DateTime(date.Year, 1, 1, 0, 0, 0, 0);
        }

        /// <summary>
        /// Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59)
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime EndOfDay(this DateTime date)
        {
            return new DateTime(date.Year, date.Month, date.Day, 23, 59, 59, 59);
        }

        /// <summary>
        /// Returns a new Date/DateTime representing the end of the month 
        /// (last day of the month; DateTime objects will have time set to 0:00)
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime EndOfMonth(this DateTime date)
        {
            var daysInMonth = DateTime.DaysInMonth(date.Year, date.Month);
            return new DateTime(date.Year, date.Month, daysInMonth, 0, 0, 0, 0);
        }

        /// <summary>
        /// Returns a new Date/DateTime representing the end of the year
        /// (last day of the month; DateTime objects will have time set to 0:00)
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime EndOfYear(this DateTime date)
        {
            var daysInMonth = DateTime.DaysInMonth(date.Year, date.Month);
            var monthsInYear = 12; // i know it is always the same :)
            return new DateTime(date.Year, monthsInYear, daysInMonth, 0, 0, 0, 0);
        }

        public static DateTime In(this DateTime date, Int32 seconds)
        {
            return date.Since(seconds);
        }

        /// <summary>
        /// Alias for MonthsAgo with last month as the value.
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime LastMonth(this DateTime date)
        {
            return date.MonthsAgo(1);
        }

        public static DateTime LastYear(this DateTime date)
        {
            return date.YearsAgo(1);
        }

        public static DateTime Midnight(this DateTime date)
        {
            return date.BeginningOfDay();
        }

        /// <summary>
        /// Alias for BeginningOfWeek
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime Monday(this DateTime date)
        {
            return date.BeginningOfWeek();
        }

        /// <summary>
        /// Returns a new Date/DateTime representing the time a number of specified months ago
        /// The value passed in for months should be Possitive
        /// </summary>
        /// <param name="date"></param>
        /// <param name="months">The positive (4) value for the number of months ago.</param>
        /// <returns></returns>
        public static DateTime MonthsAgo(this DateTime date, Int32 months)
        {
            return date.AddMonths(-months);
        }

        /// <summary>
        /// Returns a new Date/DateTime representing the time a number of specified months in the future
        /// </summary>
        /// <param name="date"></param>
        /// <param name="months">The positive (4) value for the number of months in the future.</param>
        /// <returns></returns>
        public static DateTime MonthsSince(this DateTime date, Int32 months)
        {
            return date.AddMonths(months);
        }

        /// <summary>
        /// Alias for Months Since with a 1 provided
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime NextMonth(this DateTime date)
        {
            return date.MonthsSince(1);
        }

        /// <summary>
        /// Returns a new Date/DateTime representing the start of the given day in next week (default is Monday).
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime NextWeek(this DateTime date)
        {
            var dayOfWeek = (Int32) date.DayOfWeek;
            var daysTillNextWeek = (DaysInWeek - dayOfWeek) + DaysInWeekCalcOffSet;
            var newDate = date.AddDays(daysTillNextWeek);

            return new DateTime(newDate.Year, newDate.Month, newDate.Day, 0, 0, 0, 0);
        }

        /// <summary>
        /// Shorthand for YearSince with a 1
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime NextYear(this DateTime date)
        {
            return date.YearsSince(1);
        }

        /// <summary>
        /// Converts Date to a Time (or DateTime if necessary) with the time portion set 
        /// to the beginning of the day (0:00) and then adds the specified number of seconds
        /// </summary>
        /// <param name="date"></param>
        /// <param name="seconds"></param>
        /// <returns></returns>
        public static DateTime Since(this DateTime date, Int32 seconds)
        {
            return date.BeginningOfDay().AddSeconds(seconds);
        }

        /// <summary>
        /// Convenience method which returns a new Date/DateTime representing the time 1 day since the instance time
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime Tomorrow(this DateTime date)
        {
            return date.AddDays(1);
        }

        /// <summary>
        /// Returns a new Date/DateTime representing the time a number of specified years ago
        /// </summary>
        /// <param name="date"></param>
        /// <param name="years"></param>
        /// <returns></returns>
        public static DateTime YearsAgo(this DateTime date, Int32 years)
        {
            return date.AddYears(-years);
        }

        /// <summary>
        /// Returns a new Date/DateTime representing the time a number of specified years in the future
        /// </summary>
        /// <param name="date"></param>
        /// <param name="years"></param>
        /// <returns></returns>
        public static DateTime YearsSince(this DateTime date, Int32 years)
        {
            return date.AddYears(years);
        }

        /// <summary>
        /// Convenience method which returns a new Date/DateTime representing the time 1 day ago
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime Yesterday(this DateTime date)
        {
            return date.AddDays(-1);
        }
    }
}